﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Letspirate.Common.Infrastructure;

namespace Letspirate.MapEngine
{
    public abstract class Map
    {
        #region Constants

        private const int NO_PROVINCE_ID = 0;
        private const int RIVER_VALUE = -1;
        private const int NOT_RIVER_VALUE = 0;
        private const int TRANSPARENT = 0;

        #endregion

        private readonly IStreamController _streamController;

        protected IStreamController StreamController
        {
            get { return _streamController; }
        }

        protected Map(IStreamController streamController)
        {
            _streamController = streamController;
        }

        #region Protected methods

        protected virtual Dictionary<Color, int> LoadColorIdMappings(string path)
        {
            var colorIdMappings = new Dictionary<Color, int>();

            XDocument provincesXml = XDocument.Load(path);

            var provincesElements = provincesXml.Root.Elements("Province");
            foreach (var provinceElement in provincesElements)
            {
                int id = int.Parse(provinceElement.Attribute("Id").Value);
                var color = GetColor(provinceElement.Element("Color"));
                colorIdMappings.Add(color, id);
            }

            return colorIdMappings;
        }

        protected virtual Color GetColor(XElement colorElement)
        {
            int red = int.Parse(colorElement.Attribute("Red").Value);
            int green = int.Parse(colorElement.Attribute("Green").Value);
            int blue = int.Parse(colorElement.Attribute("Blue").Value);
            return Color.FromArgb(red, green, blue);
        }

        protected virtual int[,] GetProvincesIdsMatrix(Bitmap map, Dictionary<Color, int> colorIdMappings)
        {
            var matrix = new int[map.Width, map.Height];

            for (int x = 0; x < map.Width; x++)
                for (int y = 0; y < map.Height; y++)
                {
                    var color = map.GetPixel(x, y);
                    if (colorIdMappings.Keys.Contains(color) == false)
                        matrix[x, y] = NO_PROVINCE_ID;
                    else
                        matrix[x, y] = colorIdMappings[color];
                }

            return matrix;
        }

        protected virtual Dictionary<int, IList<NeighboorInfo>> GetProvincesInfo(int[,] provincesIdsMatrix, int[,] riversMatrix)
        {
            var provincesInfo = new Dictionary<int, IList<NeighboorInfo>>();

            for (int x = 0; x < provincesIdsMatrix.GetLength(0); x++)
                for (int y = 0; y < provincesIdsMatrix.GetLength(1); y++)
                {
                    int id = provincesIdsMatrix[x, y];

                    if (provincesInfo.Keys.Contains(id) == false)
                        provincesInfo.Add(id, new List<NeighboorInfo>());

                    if (x > 0)
                    {
                        int northAdjacentId = provincesIdsMatrix[x - 1, y];
                        if (northAdjacentId != id)
                        {
                            bool isAcrossRiver = riversMatrix[x, y] == RIVER_VALUE ? true : false;//checks if there is a river
                            var neighboorInfo =
                                provincesInfo[id].FirstOrDefault(n => n.Id == northAdjacentId); //gets a neighboor with the id
                            if (neighboorInfo == null)//if there is no neighboor with the id adds a new neighboor
                                provincesInfo[id].Add(new NeighboorInfo(northAdjacentId, isAcrossRiver));
                            else if (isAcrossRiver == false)//if there is a neighboor with the id it sets crossing-river information
                                neighboorInfo.IsAcrossRiver = false;
                        }
                    }
                    if (y > 0)
                    {
                        int westAdjacentId = provincesIdsMatrix[x, y - 1];
                        if (westAdjacentId != id)
                        {
                            bool isAcrossRiver = riversMatrix[x, y - 1] == RIVER_VALUE ? true : false;//checks if there is a river
                            var neighboorInfo =
                                provincesInfo[id].FirstOrDefault(n => n.Id == westAdjacentId); //gets a neighboor with the id
                            if (neighboorInfo == null)//if there is no neighboor with the id adds a new neighboor
                                provincesInfo[id].Add(new NeighboorInfo(westAdjacentId, isAcrossRiver));
                            else if (isAcrossRiver == false)//if there is a neighboor with the id it sets crossing-river information
                                neighboorInfo.IsAcrossRiver = false;
                        }
                    }
                    if (x < provincesIdsMatrix.GetLength(0) - 1)
                    {
                        int southAdjacentId = provincesIdsMatrix[x + 1, y];
                        if (southAdjacentId != id)
                        {
                            bool isAcrossRiver = riversMatrix[x + 1, y] == RIVER_VALUE ? true : false;//checks if there is a river
                            var neighboorInfo =
                                provincesInfo[id].FirstOrDefault(n => n.Id == southAdjacentId); //gets a neighboor with the id
                            if (neighboorInfo == null)//if there is no neighboor with the id adds a new neighboor
                                provincesInfo[id].Add(new NeighboorInfo(southAdjacentId, isAcrossRiver));
                            else if (isAcrossRiver == false)//if there is a neighboor with the id it sets crossing-river information
                                neighboorInfo.IsAcrossRiver = false;
                        }
                    }
                    if (y < provincesIdsMatrix.GetLength(1) - 1)
                    {
                        int eastAdjacentId = provincesIdsMatrix[x, y + 1];
                        if (eastAdjacentId != id)
                        {
                            bool isAcrossRiver = riversMatrix[x, y + 1] == RIVER_VALUE ? true : false;//checks if there is a river
                            var neighboorInfo =
                                provincesInfo[id].FirstOrDefault(n => n.Id == eastAdjacentId); //gets a neighboor with the id
                            if (neighboorInfo == null)//if there is no neighboor with the id adds a new neighboor
                                provincesInfo[id].Add(new NeighboorInfo(eastAdjacentId, isAcrossRiver));
                            else if (isAcrossRiver == false)//if there is a neighboor with the id it sets crossing-river information
                                neighboorInfo.IsAcrossRiver = false;
                        }
                    }
                }

            return provincesInfo;
        }

        protected virtual int[,] GetRiversMatrix(Bitmap map)
        {
            var matrix = new int[map.Width, map.Height];

            for (int y = 0; y < map.Height; y++)
                for (int x = 0; x < map.Width; x++)
                    if (map.GetPixel(x, y).A == TRANSPARENT)
                        matrix[x, y] = NOT_RIVER_VALUE;
                    else
                        matrix[x, y] = RIVER_VALUE;

            return matrix;
        }

        protected virtual int[,] AddRiversToProvincesIdsMatrix(int[,] provincesIdsMatrix, int[,] riversMatrix)
        {
            for (int x = 0; x < riversMatrix.GetLength(0); x++)
                for (int y = 0; y < riversMatrix.GetLength(1); y++)
                    if (riversMatrix[x, y] == RIVER_VALUE)
                        provincesIdsMatrix[x, y] = RIVER_VALUE;

            return provincesIdsMatrix;
        }

        /// <summary>
        /// Writes the provinces' ids matrix into the stream. 
        /// <para>Structure of the writing:</para>
        /// [int-width][int-height][int-x0,y0][int-x0,y1]...[int-x1,y0][int-x1,y1]...[int-xN,yM]
        /// </summary>
        /// <param name="path">The path to the source file.</param>
        /// <param name="provincesIdsMatrix">The provinces' ids matrix.</param>
        protected virtual void SaveProvincesIdsMatrix(string path, int[,] provincesIdsMatrix)
        {
            using (var stream = StreamController.CreateStream(path))
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(provincesIdsMatrix.GetLength(0));
                writer.Write(provincesIdsMatrix.GetLength(1));

                for (int x = 0; x < provincesIdsMatrix.GetLength(0); x++)
                    for (int y = 0; y < provincesIdsMatrix.GetLength(1); y++)
                        writer.Write(provincesIdsMatrix[x, y]);

                writer.Flush();
            }
        }

        /// <summary>
        /// Writes the provinces' ids and their neighboors info into the stream. 
        /// <para>Structure of the writing:</para>
        /// [int-number of provinces][int-province1's id][int-count of province1's neighboors][int-id of province1's neighboor1][bool-is province1's neighboor1 across river?]...[int-id of province1's neighboorN][bool-is province1's neighboorN across river?]...[int-provinceM's id][int-count of provinceM's neighboors][int-id of provinceM's neighboor1][bool-is provinceM's neighboor1 across river?]...[int-id of provinceM's neighboorK][bool-is provinceM's neighboorK across river?]
        /// </summary>
        /// <param name="path">The path to the source file.</param>
        /// <param name="provincesInfo">The provinces and their neighboors.</param>
        protected virtual void SaveProvincesInfo(string path, Dictionary<int, IList<NeighboorInfo>> provincesInfo)
        {
            using (var stream = StreamController.CreateStream(path))
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(provincesInfo.Count());

                foreach (var provinceInfo in provincesInfo)
                {
                    writer.Write(provinceInfo.Key);
                    writer.Write(provinceInfo.Value.Count);

                    foreach (var neighboor in provinceInfo.Value)
                    {
                        writer.Write(neighboor.Id);
                        writer.Write(neighboor.IsAcrossRiver);
                    }
                }

                writer.Flush();
            }
        }

        protected virtual int[,] LoadProvincesIdsMatrix(string path)
        {
            int[,] provincesIdsMatrix;

            using (var stream = StreamController.OpenStream(path))
            using (var reader = new BinaryReader(stream))
            {
                var width = reader.ReadInt32();
                var height = reader.ReadInt32();

                provincesIdsMatrix = new int[width,height];

                for (int x = 0; x < width; x++)
                    for (int y = 0; y < height; y++)
                        provincesIdsMatrix[x, y] = reader.ReadInt32();
            }

            return provincesIdsMatrix;
        }

        protected virtual Dictionary<int, IList<NeighboorInfo>> LoadProvincesInfo(string path)
        {
            var provincesInfo = new Dictionary<int, IList<NeighboorInfo>>();

            using (var stream = StreamController.OpenStream(path))
            using (var reader = new BinaryReader(stream))
            {
                int numberOfProvinces = reader.ReadInt32();

                for (int provinceNumber = 0; provinceNumber < numberOfProvinces; provinceNumber++)
                {
                    var provinceId = reader.ReadInt32();
                    var numberOfNeighboors = reader.ReadInt32();
                    var neighboors = new List<NeighboorInfo>();

                    for (int neighboorNumber = 0; neighboorNumber < numberOfNeighboors; neighboorNumber++)
                    {
                        var neighboorId = reader.ReadInt32();
                        var neighboorIsAcrossRiver = reader.ReadBoolean();
                        neighboors.Add(new NeighboorInfo(neighboorId, neighboorIsAcrossRiver));
                    }

                    provincesInfo[provinceId] = neighboors;
                }
            }

            return provincesInfo;
        }

        #endregion
    }
}